package org.massi.imslp.query;


import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import net.sourceforge.jwbf.core.actions.util.ActionException;
import net.sourceforge.jwbf.core.contentRep.SimpleArticle;
import net.sourceforge.jwbf.mediawiki.bots.MediaWikiBot;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.massi.imslp.query.WorkInfo.Builder;

import de.tudarmstadt.ukp.wikipedia.parser.ParsedPage;
import de.tudarmstadt.ukp.wikipedia.parser.Section;
import de.tudarmstadt.ukp.wikipedia.parser.Template;
import de.tudarmstadt.ukp.wikipedia.parser.mediawiki.MediaWikiParser;
import de.tudarmstadt.ukp.wikipedia.parser.mediawiki.MediaWikiParserFactory;


/**
 * {@link IImslpQuery} implementation. Uses jwbf library to perform search and to retrieve a single
 * page. MediaWiki parser library is used for parsing the wiki page content
 * 
 */
public class ImslpQueryImpl implements IImslpQuery
{
	/**
	 * class logger
	 */
	private static final Logger LOG = Logger.getLogger(ImslpQueryImpl.class);
	/**
	 * credentials to access the wiki
	 */
	private ICredentials credentials;

	/**
	 * base URL for the IMSLP wiki
	 */
	private final String imslpUrl = "http://imslp.org/";

	/**
	 * the page parser
	 */
	private final PageParser parser = new PageParser();

	/**
	 * Visit a {@link ParsedPage} to build a {@link IWorkInfo}
	 */
	private static final class PageVisitor implements IParsedPageVisitor
	{
		/**
		 * the instance built during parsing
		 */
		private IWorkInfo workInfo;
		/**
		 * the builder instance used to create {@link IWorkInfo} instance
		 */
		private WorkInfo.Builder builder;


		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.massi.imslp.query.IParsedPageVisitor#onPageEnd(de.tudarmstadt.ukp.wikipedia.parser
		 * .ParsedPage)
		 */
		@Override
		public void onPageEnd(final ParsedPage page)
		{
			LOG.debug("now building work info");
			// now we can build the instance, as we've collected all the parameters
			this.workInfo = this.builder.build();
		}


		/*
		 * (non-Javadoc)
		 * 
		 * @see org.massi.imslp.query.IParsedPageVisitor#onParameter(java.lang.String,
		 * java.lang.String)
		 */
		@Override
		public void onParameter(final String key, final String value)
		{
			// check if this is a supported parameter
			final TemplateParameter parameter = TemplateParameter.fromName(key);
			if (parameter != null)
			{
				LOG.debug(MessageFormat.format("now setting builder value using key [{0}] and value [{1}]", key, value));
				// invoke the build method on parameter
				parameter.build(this.builder, value);
			}
		}


		@Override
		public void onTemplate(final Template template)
		{
			// do nothing
		}


		@Override
		public void onSection(final Section section)
		{
			// do nothing
		}


		@Override
		public void onPageStart(final ParsedPage page)
		{
			// parsing starts, let's create a new builder
			this.builder = new WorkInfo.Builder();
		}


		/**
		 * Return the {@link IWorkInfo} instance built by the {@link Builder}
		 * 
		 * @return the {@link IWorkInfo} created by the builder.
		 */
		public IWorkInfo getWorkInfo()
		{
			return this.workInfo;
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.massi.imslp.query.IImslpQuery#query(org.massi.imslp.query.IQueryParameters)
	 */
	@Override
	public List<String> query(final IQueryParameters parameters) throws WikiException
	{
		// parameters can not be null
		assert parameters != null;
		try
		{
			final MediaWikiBot bot = prepareBot();

			final CustomQuery custom = new CustomQuery(bot, parameters.toQueryString());
			final Iterator<String> iterator = custom.iterator();
			final List<String> allTitles = new ArrayList<String>();
			while (iterator.hasNext())
			{
				final String title = iterator.next();
				allTitles.add(title);
			}
			LOG.debug(MessageFormat.format("Found [{0}] titles", allTitles.size()));
			return Collections.unmodifiableList(allTitles);
		}
		catch (final Exception e)
		{
			LOG.error("Cold not query IMSLP wiki using parameters:" + parameters, e);
			throw new WikiException(e);
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.massi.imslp.query.IImslpQuery#setCredentials(org.massi.imslp.query.ICredentials)
	 */
	@Override
	public void setCredentials(final ICredentials credentials)
	{
		this.credentials = credentials;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.massi.imslp.query.IImslpQuery#fetch(java.lang.String)
	 */
	@Override
	public IComposerWork fetch(final String pageName) throws WikiException
	{
		try
		{
			// get the content using JWBF and then parse using MediaWikiParser. We then visit the
// page and collect the relevant parameters
			final MediaWikiBot bot = prepareBot();
			final SimpleArticle sa = new SimpleArticle(
					bot.readContent(pageName));
			final MediaWikiParserFactory pf = new MediaWikiParserFactory();
			final MediaWikiParser parser = pf.createParser();
			final String text = sa.getText();
			LOG.debug("Retrieved Wiki text\n" + text);
			final ParsedPage pp = parser.parse(text);
			final IWorkInfo work = visit(pp);
			final String composer = StringUtils.substringBetween(pageName, "(", ")");
			final ComposerWork composerWork = new ComposerWork(work, composer);
			LOG.info("Fetched work: " + composerWork);
			return composerWork;
		}
		catch (final Exception e)
		{
			LOG.error("Cold not fetch work using page title:" + pageName, e);
			throw new WikiException(e);
		}
	}


	/**
	 * Inspect the {@link ParsedPage} using a visitor pattern
	 * 
	 * @param page the page to be inspect
	 * @return a new instance of {@link IWorkInfo}
	 * @see PageVisitor
	 */
	private IWorkInfo visit(final ParsedPage page)
	{
		final PageVisitor visitor = new PageVisitor();
		this.parser.parse(page, visitor);
		return visitor.getWorkInfo();
	}


	/**
	 * Create and return a new instance of {@link MediaWikiBot} with the base URL of the Wiki. It
	 * also performs the authentication using the injected credentials, if they are not
	 * <code>null</code>
	 * 
	 * @return the {@link MediaWikiBot} instance
	 * @throws MalformedURLException
	 * @throws ActionException
	 * @see #imslpUrl
	 * @see ICredentials
	 */
	private MediaWikiBot prepareBot() throws MalformedURLException, ActionException
	{
		final MediaWikiBot bot = new MediaWikiBot(this.imslpUrl);
		if (this.credentials != null)
		{
			bot.login(this.credentials.getUsername(), this.credentials.getPassword());
		}
		return bot;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		return "ImslpQueryImpl [imslpUrl=" + this.imslpUrl + "]";
	}

}
